﻿/* 
 * Copyright (c) 2008 Intel Corporation
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * -- Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * -- Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * -- Neither the name of the Intel Corporation nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE INTEL OR ITS
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using OpenMetaverse;
using OpenMetaverse.StructuredData;
using OpenMetaverse.Messages.CableBeach;

namespace AssetClient
{
    public class Program
    {
        static readonly Uri DEFAULT_SERVER_URL = new Uri("http://localhost:9001");

        static int verbosity;
        static NDesk.Options.OptionSet argParser;

        static void Main(string[] args)
        {
            bool showhelp = false;
            Uri serverUrl = DEFAULT_SERVER_URL;
            Uri claimedIdentifier = null;
            UUID assetID = UUID.Zero;
            UUID authToken = UUID.Zero;
            string inputFile = null;
            string operation = null;
            string outputFile = null;
            string firstName = null;
            string lastName = null;
            string password = null;

            argParser = new NDesk.Options.OptionSet()
                .Add("s|server=", "URL of the asset server (default is 'http://localhost:9001/')", delegate(string v) { showhelp = !Uri.TryCreate(v, UriKind.Absolute, out serverUrl);
                    System.Console.WriteLine(showhelp);
                })
                .Add("a|assetid=", "asset ID to fetch data or metadata for", delegate(string v) { showhelp = !UUID.TryParse(v, out assetID);
                    System.Console.WriteLine(showhelp);
                })
                .Add("i|identity=", "OpenID identity to authenticate as", delegate(string v) { showhelp = !Uri.TryCreate(v, UriKind.Absolute, out claimedIdentifier);
                    System.Console.WriteLine(showhelp);
                })
                .Add("f|first=", "first name of the avatar to authenticate as", delegate(string v) { firstName = v; })
                .Add("l|last=", "last name of the avatar to authenticate as", delegate(string v) { lastName = v; })
                .Add("p|pass=", "password of the avatar to authenticate as", delegate(string v) { password = v; })
                .Add("h|?|help", delegate(string v) { showhelp = (v != null); })
                .Add("I|input-file=", "asset data to upload with createasset", delegate(string v) { inputFile = v; })
                .Add("o|operation=", "data|metadata|createasset (default is 'data')", delegate(string v) { operation = v; })
                .Add("O|output-file=", "file to write fetched data to. If 'default', the asset ID will be used as the filename", delegate(string v) { outputFile = v; })
                .Add("v|verbose", delegate(string v) { if (v != null) ++verbosity; });
            argParser.Parse(args);

            if (!showhelp)
            {
                if (claimedIdentifier != null)
                {
                    bool success = false;

                    if (!String.IsNullOrEmpty(firstName) && !String.IsNullOrEmpty(lastName) && !String.IsNullOrEmpty(password))
                    {
                        success = AssetClient.TryGetAuthToken(claimedIdentifier, firstName, lastName, password, serverUrl, out authToken);
                    }
                    else
                    {
                        ShowHelp();
                        return;
                    }

                    if (!success)
                    {
                        Console.WriteLine("[ERROR] Failed to claim the identifier {0}", claimedIdentifier);
                        return;
                    }
                    else if (verbosity > 0)
                    {
                        Console.WriteLine("Authenticated, authToken=" + authToken.ToString());
                    }
                }

                if (operation == null)
                    operation = "data";

                switch (operation)
                {
                    case "data":
                        if (assetID != UUID.Zero)
                            FetchMetadataForData(assetID, authToken, serverUrl, outputFile);
                        else
                            ShowHelp();
                        break;
                    case "metadata":
                        if (assetID != UUID.Zero)
                            FetchMetadata(assetID, authToken, serverUrl, outputFile);
                        else
                            ShowHelp();
                        break;
                    case "createasset":
                        CreateAsset(assetID, authToken, serverUrl, inputFile);
                        break;
                    default:
                        Console.WriteLine("Unrecognized operation: " + operation);
                        break;
                }
            }
            else
            {
                ShowHelp();
            }
        }

        static void FetchMetadataForData(UUID assetID, UUID authToken, Uri serverUrl, string outputFile)
        {
            if (verbosity > 0)
                Console.WriteLine(String.Format("Starting metadata fetch for asset {0}", assetID));

            // Fetch the metadata first to locate this asset, parse the metadata, then fetch the actual asset
            byte[] metadataBytes;
            if (AssetClient.TryGetMetadata(assetID, authToken, serverUrl, out metadataBytes))
            {
                string jsonString = System.Text.Encoding.UTF8.GetString(metadataBytes);

                if (!String.IsNullOrEmpty(jsonString))
                {
                    OSD metadata = OSDParser.DeserializeJson(jsonString);
                    OSDMap map = metadata as OSDMap;

                    if (map != null && map.ContainsKey("methods"))
                    {
                        OSDMap methods = map["methods"] as OSDMap;
                        Uri assetLocation = methods["data"].AsUri();

                        if (assetLocation != null)
                        {
                            if (verbosity > 0)
                                Console.WriteLine("Metadata retrieved, fetching asset data from " + assetLocation);

                            FetchData(assetID, authToken, assetLocation, outputFile);
                        }
                        else
                        {
                            Console.WriteLine("Metadata did not contain a method for retrieving asset data:");
                            Console.WriteLine(jsonString);
                        }
                    }
                    else
                    {
                        Console.WriteLine("Failed parsing metadata:");
                        Console.WriteLine(jsonString);
                    }
                }
                else
                {
                    Console.WriteLine("Empty metadata returned from the server");
                }
            }
        }

        static void FetchData(UUID assetID, UUID authToken, Uri assetUrl, string outputFile)
        {
            if (verbosity > 0)
                Console.WriteLine(String.Format("Starting data fetch for asset {0} from {1}", assetID, assetUrl));

            byte[] assetData;
            if (AssetClient.TryGetAsset(assetID, authToken, assetUrl, out assetData))
            {
                if (verbosity > 0)
                    Console.WriteLine(String.Format("Downloaded a {0} byte asset", assetData.Length));

                try
                {
                    Stream outStream;

                    if (outputFile == "default")
                        outputFile = assetID.ToString();

                    if (!String.IsNullOrEmpty(outputFile))
                        outStream = File.Open(outputFile, FileMode.Create, FileAccess.Write);
                    else
                        outStream = Console.OpenStandardOutput();

                    int pos = 0;
                    while (pos < assetData.Length)
                    {
                        int len = (assetData.Length - pos < 4096) ? assetData.Length - pos : 4096;
                        outStream.Write(assetData, pos, len);
                        pos += len;
                    }

                    outStream.Close();
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Error writing the asset: " + ex.Message);
                }
            }
            else
            {
                Console.WriteLine("Failed fetching asset " + assetID.ToString());
                return;
            }
        }

        static void FetchMetadata(UUID assetID, UUID authToken, Uri serverUrl, string outputFile)
        {
            if (verbosity > 0)
                Console.WriteLine("Starting metadata fetch for asset " + assetID.ToString());

            byte[] metadata;
            if (AssetClient.TryGetMetadata(assetID, authToken, serverUrl, out metadata))
            {
                if (verbosity > 0)
                    Console.WriteLine(String.Format("Downloaded {0} bytes of metadata", metadata.Length));

                try
                {
                    Stream outStream;

                    if (outputFile == "default")
                        outputFile = assetID.ToString() + ".txt";

                    if (!String.IsNullOrEmpty(outputFile))
                        outStream = File.Open(outputFile, FileMode.Create, FileAccess.Write);
                    else
                        outStream = Console.OpenStandardOutput();

                    int pos = 0;
                    while (pos < metadata.Length)
                    {
                        int len = (metadata.Length - pos < 1024) ? metadata.Length - pos : 1024;
                        outStream.Write(metadata, pos, len);
                        pos += len;
                    }

                    outStream.Close();
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Error writing the metadata: " + ex.Message);
                }
            }
            else
            {
                Console.WriteLine("Failed fetching metadata for asset " + assetID.ToString());
                return;
            }
        }

        static void CreateAsset(UUID assetID, UUID authToken, Uri serverUrl, string inputFile)
        {
            if (verbosity > 0)
            {
                if (assetID != UUID.Zero)
                    Console.WriteLine(String.Format("Starting asset creation for asset {0} from file {1}", assetID, inputFile));
                else
                    Console.WriteLine("Starting asset creation from file " + inputFile);
            }

            byte[] assetData;
            try { assetData = File.ReadAllBytes(inputFile); }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                return;
            }

            string name = Path.GetFileNameWithoutExtension(inputFile);
            string extension = Path.GetExtension(inputFile).TrimStart('.').ToLower();
            string contentType = CableBeachUtils.ExtensionToContentType(extension);

            assetID = AssetClient.CreateAsset(name, "Created with AssetClient", contentType, false, authToken,
                serverUrl, assetData, assetID);

            if (assetID != UUID.Zero)
                Console.WriteLine("Created asset " + assetID.ToString());
            else
                Console.WriteLine("Failed to create asset");
        }

        static void ShowHelp()
        {
            Console.WriteLine("Usage: AssetClient.exe [OPTION]...");
            Console.WriteLine("A non-interactive client for the asset server");
            Console.WriteLine("Options:");
            argParser.WriteOptionDescriptions(Console.Out);
        }
    }
}
